﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  OcsEndpointProxy.cs
//    
// Abstract:
//
//  This class wraps all of the interaction that the Dispatcher needs
//  with an OCS endpoint.
//  
//--

using System;
using System.Diagnostics;
using System.Net;
using System.Runtime.Serialization;
using System.Timers;
using Microsoft.OfficeCommunicationsServer.Applications.Common;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;


namespace Microsoft.EnhancedReminders.Dispatcher
{
    /// <summary>
    /// Entry point for objects that need to interact with a CollaborationEndpoint.
    /// This class takes care of the internal logic needed to connect and maintain a
    /// connection to OCS.
    /// </summary>
	public class OcsEndpointProxy : IDisposable, IOcsEndpointProxy
	{
        /// <summary>
        /// Client to be used across all OCS dependent components.
        /// </summary>
        private UserEndpoint _clientEndpoint;
        private CollaborationPlatform _collabPlatform;

        /// <summary>
        /// Timer used for managing reconnects in the event of a lost connection to OCS.
        /// </summary>
        private Timer _restartTimer = new Timer(TimeSpan.FromSeconds(60.0).TotalMilliseconds);

        /// <summary>
        /// Event Handler for notifying listeners when the registration state of the
        /// SIP endpoint has changed.
        /// </summary>
        public event EventHandler<LocalEndpointStateChangedEventArgs> RegistrationStateChanged;

        /// <summary>
        /// Synchronously initialize the OcsEndpointProxy. This includes setting up the
        /// CollaborationPlatform and establishing the CollaborationEndpoint.
        /// </summary>
        public void Initialize()
        {
            string sipAddress = "sip:" + Environment.UserName + "@" + Helpers.GetSetting<string>("BotName", null); ;

            try
            {
                ClientPlatformSettings platformSettings = new ClientPlatformSettings("(Enhanced_Reminders)", SipTransportType.Tls);
                platformSettings.DefaultAudioVideoProviderEnabled= true;
                _collabPlatform = new CollaborationPlatform(platformSettings);
                UserEndpointSettings uepSettings = new UserEndpointSettings(sipAddress, Helpers.GetSetting<string>("SipServer",null));
                _clientEndpoint = new UserEndpoint(_collabPlatform, uepSettings);
                _clientEndpoint.Credential = CredentialCache.DefaultNetworkCredentials;
                _clientEndpoint.StateChanged += OnRegistrationStateChanged;

                // Because this method should only be called on service startup when there is no work to be done,
                // we'll make our calls for establishing our endpoint synchronously.
                Logger.WriteTraceVerbose(Context.Global, "Initializing collaboration platform.");
                _collabPlatform.InstantMessagingSettings.SupportedFormats = InstantMessagingFormat.PlainText;
                _collabPlatform.EndStartup(_collabPlatform.BeginStartup(null, null));

                Logger.WriteTraceVerbose(Context.Global, "Initializing client endpoint.");
                _clientEndpoint.EndEstablish(_clientEndpoint.BeginEstablish(null, null));
            }
            catch (AuthenticationException e)
            {
                Logger.WriteTraceError(e, "Fatal Error: AuthenticationException.");
                throw;
            }
            catch (ArgumentException e)
            {
                OperationalLogs.LogSipUnreachableError(Helpers.GetSetting<string>("SipServer",null), e);
                throw;
            }
            catch (ConnectionFailureException e)
            {
                OperationalLogs.LogSipUnreachableError(_clientEndpoint.OwnerUri, e);
                throw;
            }
            catch (OperationTimeoutException e)
            {
                OperationalLogs.LogSipRegistrationError(_clientEndpoint.OwnerUri, e);
                throw;
            }
            catch (RegisterException e)
            {
                OperationalLogs.LogSipRegistrationError(_clientEndpoint.OwnerUri, e);
                throw;
            }
        }

        /// <summary>
        /// Synchronously disconnects from OCS and shuts down the Collaboration Platform.
        /// </summary>
        public void Uninitialize()
        {
            if (_restartTimer.Enabled)
            {
                _restartTimer.Stop();
            }

            if (_clientEndpoint.State == LocalEndpointState.Established)
            {
                _clientEndpoint.EndTerminate(_clientEndpoint.BeginTerminate(null, null));
            }

            _collabPlatform.EndShutdown(_collabPlatform.BeginShutdown(null, null));            
        }

        /// <summary>
        /// Creates a new Conversation for implementations of IMessageSender to use
        /// </summary>
        public Conversation CreateConversation(string targetAddress)
        {
            Conversation conversation = new Conversation(_clientEndpoint);
            conversation.ApplicationContext = targetAddress;

            return conversation;
        }

        /// <summary>
        /// Obtains an appropriate subclass of IPresenceProvider for doing presence
        /// lookups.
        /// </summary>
        /// <returns>An instance of IPresenceProvider.</returns>
        public IPresenceProvider CreatePresenceProvider()
        {
            return new UcmaPresenceProvider(_clientEndpoint);
        }

        private void OnRegistrationStateChanged(object sender, LocalEndpointStateChangedEventArgs e)
        {
            Debug.Assert(e != null, "e != null");
            Logger.WriteTraceInformation("ClientEndpoint registration state has changed to " + e.State);

            EventHandler<LocalEndpointStateChangedEventArgs> callback = RegistrationStateChanged;

            if (callback != null)
            {
                callback(sender, e);
            }

            if (e.State == LocalEndpointState.Terminated)
            {
                if (!_restartTimer.Enabled)
                {
                    _restartTimer.Elapsed += new System.Timers.ElapsedEventHandler(RestartTimerElapsed);
                    _restartTimer.Start();
                }
            }
        }

        private void RestartTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            _restartTimer.Elapsed -= RestartTimerElapsed;
            _restartTimer.Stop();

            ReestablishEndpoint();
        }

        /// <summary>
        /// Attempts to re-establish the client endpoint's connection to OCS if that connection
        /// has been severed.
        /// </summary>
        private void ReestablishEndpoint()
        {
            Debug.Assert(_clientEndpoint != null && _clientEndpoint.State == LocalEndpointState.Terminated, "_clientEndpoint != null && _clientEndpoint.State == LocalEndpointState.Terminated");
            string sipAddress = "sip:" + Environment.UserName + "@" + Helpers.GetSetting<string>("BotDomain",null);

            // We can't just re-establish the endpoint; its state needs to be idle for us to reconnect, so
            // we'll create a new one.
            _clientEndpoint.StateChanged -= OnRegistrationStateChanged;
            UserEndpointSettings uepSettings = new UserEndpointSettings(sipAddress, Helpers.GetSetting<string>("SipServer", null));
            _clientEndpoint = new UserEndpoint(_collabPlatform, uepSettings);
            _clientEndpoint.Credential = CredentialCache.DefaultNetworkCredentials;
            _clientEndpoint.StateChanged += OnRegistrationStateChanged;

            _clientEndpoint.BeginEstablish(ReestablishComplete, _clientEndpoint);
        }

        private void ReestablishComplete(IAsyncResult asyncResult)
        {
            Debug.Assert(_clientEndpoint != null, "_clientEndpoint != null");

            try
            {
                _clientEndpoint.EndEstablish(asyncResult);
                Logger.WriteTraceInformation("Dispatcher endpoint re-established.");
            }
            catch (AuthenticationException e)
            {
                Logger.WriteTraceError(e, "Fatal Error: AuthenticationException.");
                RestartConnectionTimer();
            }
            catch (ConnectionFailureException e)
            {
                OperationalLogs.LogSipUnreachableError(_clientEndpoint.OwnerUri, e);
                RestartConnectionTimer();
            }
            catch (OperationTimeoutException e)
            {
                OperationalLogs.LogSipRegistrationError(_clientEndpoint.OwnerUri, e);
                RestartConnectionTimer();
            }
            catch (RegisterException e)
            {
                OperationalLogs.LogSipRegistrationError(_clientEndpoint.OwnerUri, e);
                RestartConnectionTimer();
            }
        }

        private void RestartConnectionTimer()
        {
            if (!_restartTimer.Enabled)
            {
                _restartTimer.Elapsed += new System.Timers.ElapsedEventHandler(RestartTimerElapsed);
                _restartTimer.Start();
            }
        }

        #region IDisposable Members

        /// <summary>
        /// Disposes of the timer used for reconnecting to OCS
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_restartTimer != null)
                {
                    _restartTimer.Dispose();
                    _restartTimer = null;
                }
            }

        }
        #endregion
    }
}
